﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using DNT.RavenQA.Core.Extensions;

namespace DNT.RavenQA.Core.Models
{
    public abstract class User : Entity<Int32>, IPrincipal, INamedDocument, IEquatable<DenormalizedReference<User>>
    {
        public String Name
        {
            get
            {
                EnsureName();
                return _name;
            }
            protected set { _name = value; }
        }
        private String _name;

        private void EnsureName()
        {
            if (_name.IsNullOrWhiteSpace())
                _name = String.Format("user_{0}", new Random().NextString(10));
        }

        public void ChangeName(String newValue)
        {
            newValue.EnsureIsNotNullOrEmpty("newValue");
            Name = newValue;
        }

        public String EmailAddress { get; protected set; }

        private const String BuiltInProviderName = "BultIn";

        private String _providerName = BuiltInProviderName;

        public String ProviderName
        {
            get { return _providerName; }
            protected set
            {
                value.EnsureIsNotNullOrEmpty("value");
                _providerName = value;
            }
        }

        public IIdentity Identity
        {
            get { return new GenericIdentity(Name, ProviderName); }
        }

        public Int32 ContributionsCount { get; protected set; }

        internal void IncrementContributionsCount()
        {
            ContributionsCount++;
        }

        public Boolean IsInRole(String role)
        {
            return _roles.Contains(role);
        }

        public void AssignRole(String role)
        {
            role.EnsureIsNotNullOrEmpty("role");

            if (_roles.Contains(role))
                throw new InvalidOperationException(String.Format("Role '{0}' is already assigned.", role));

            _roles.Add(role);
        }

        public void DeassignRole(String role)
        {
            role.EnsureIsNotNullOrEmpty("role");

            if (!_roles.Contains(role))
                throw new InvalidOperationException(String.Format("Role '{0}' is not assigned.", role));

            _roles.Add(role);
        }

        protected ISet<String> Roles
        {
            get { return _roles; }
            set { _roles = value; }
        }
        private ISet<String> _roles = new HashSet<String>(StringComparer.OrdinalIgnoreCase);

        private readonly IDictionary<Int32, String> _gravatars = new Dictionary<Int32, String>();

        private void LoadGravatarUrl(Int32 size)
        {
            if (_gravatars.ContainsKey(size))
                return;

            var md5 = MD5.Create();
            var data = md5.ComputeHash(Encoding.Default.GetBytes(EmailAddress));
            var sb = new StringBuilder();

            for (var i = 0; i < data.Length; i++)
                sb.Append(data[i].ToString("x2"));

            _gravatars.Add(size, String.Format("http://www.gravatar.com/avatar/{0}?s={1}", sb, size));
        }

        public String GravatarUrl
        {
            get { return GetGravatarUrl(32); }
        }

        public String GetGravatarUrl(Int32 size)
        {
            LoadGravatarUrl(size);
            return _gravatars[size];
        }

        public override Int32 GetHashCode()
        {
            unchecked
            {
                return (ProviderName.GetHashCode() * 397) ^ EmailAddress.GetHashCode();
            }
        }

        public Boolean Equals(DenormalizedReference<User> other)
        {
            return !ReferenceEquals(null, other) &&
                   String.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase);
        }

        public override Boolean Equals(Object obj)
        {
            return base.Equals(obj);
        }

        public Boolean Equals(User other)
        {
            if (ReferenceEquals(null, other))
                return false;

            if (ReferenceEquals(this, other))
                return true;

            return String.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase) ||
                   (String.Equals(ProviderName, other.ProviderName, StringComparison.OrdinalIgnoreCase) &&
                    String.Equals(EmailAddress, other.EmailAddress, StringComparison.OrdinalIgnoreCase));
        }

        public static Boolean operator ==(User left, User right)
        {
            return Equals(left, right);
        }

        public static Boolean operator !=(User left, User right)
        {
            return !Equals(left, right);
        }
    }
}